import { Vector3 } from "@babylonjs/core/Maths/math.vector";
import { PointerInfo } from "@babylonjs/core/Events/pointerEvents";
import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh";
import { Scene } from "@babylonjs/core/scene";
import { AnimationFrame } from '../animation/animation-frame';
import { Nullable } from "@babylonjs/core/types";
import { PickingInfo } from "@babylonjs/core/Collisions/pickingInfo";
import { Calculation } from "../tool/calculation";
import { Helper } from "../tool/helper";


/**
 * 点击拾取参数对象
 * @param defaultPickable 默认场景物体是否可拾取
 * @param isEnabled 是否启用
 * @param clickThreshold 点击阈值（与拖动区别）
 * @param doubleClickInterval 双击间隔阈值
 * @param doubleClickEnabled 是否启用双击
 * @param clickEnabled 是否启用单击
 * @param moveEnabled 是否启用指针移动
 * @param isPressMove 指针移动是否必须在按下时
 * @param pressEnabled 是否启用指针按住
 * @param pressThreshold 指针按住的时长阈值
 * @param downEnabled 是否启用指针按下
 */
interface IAbstractPickOptions {
    defaultPickable?: boolean,
    isEnabled?: boolean,
    clickThreshold?: number,
    doubleClickInterval?: number,
    doubleClickEnabled?: boolean,
    clickEnabled?: boolean,
    moveEnabled?: boolean,
    isPressMove?: boolean,
    pressEnabled?: boolean,
    pressThreshold?: number,
    downEnabled?: boolean,
}


/**
 * 拾取抽象类
 * @param scene 所在场景
 * @param options 参数对象
 */
abstract class AbstractPick<T> {

    private _isEnabled = false;
    private _clickStart = false;
    private _doubleClickStart = false;
    private _pointerHandle: { (info: PointerInfo): void };
    private _clickTimer = {};
    private _dblClickTimer = {};
    private _pressTimer = {};

    protected pointerStart = new Vector3();
    protected pointerEnd = new Vector3();

    /**
     * 所在场景
     */
    public readonly scene: Scene;

    /**
     * 可拾取对象
     */
    public readonly targets: T[] = [];

    constructor(scene: Scene, options?: IAbstractPickOptions) {
        this.scene = scene;
        const ops = options || {};
        if (ops.defaultPickable !== undefined) {
            this.setSceneMeshes(ops.defaultPickable);
        }
        const clickThres = ops.clickThreshold || 1;
        const doubleClickThres = ops.doubleClickInterval || 200;
        const doubleClickEnabled = ops.doubleClickEnabled === undefined ? true : ops.doubleClickEnabled;
        const clickEnabled = ops.clickEnabled === undefined ? true : ops.clickEnabled;
        const moveEnabled = ops.moveEnabled === undefined ? false : ops.moveEnabled;
        const isPressMove = ops.isPressMove === undefined ? false : ops.isPressMove;
        const downEnabled = ops.downEnabled === undefined ? false : ops.downEnabled;
        const pressEnabled = ops.pressEnabled === undefined ? false : ops.pressEnabled;
        const pressThreshold = ops.pressThreshold === undefined ? 200 : ops.pressThreshold;

        this._pointerHandle = (info: PointerInfo) => {
            const pickInfo = Helper.pickInfo(info, this.scene);
            if (!pickInfo) return;
            switch (info.event.type) {
                case 'pointerdown':
                    if (downEnabled) {
                        this.downHandle(pickInfo);
                    }
                    if (pressEnabled) {
                        AnimationFrame.throttleByTime(this._pressTimer, pressThreshold, () => {
                            this.pressHandle(pickInfo);
                        }, false);
                    }

                    this._clickStart = true;
                    this.pointerStart.copyFrom(pickInfo.ray!.direction);
                    if (!doubleClickEnabled) return;
                    if (this._doubleClickStart) {
                        AnimationFrame.cancel(this._clickTimer);
                        AnimationFrame.cancel(this._dblClickTimer);
                        this._doubleClickStart = true;
                    }
                    break;
                case 'pointermove':
                    if (moveEnabled && (!isPressMove)) {
                        this.moveHandle(pickInfo);
                    }
                    if (!this._clickStart) return;
                    if (moveEnabled && isPressMove) {
                        this.moveHandle(pickInfo);
                    }

                    this.pointerEnd.copyFrom(pickInfo.ray!.direction);
                    if (Calculation.angleOfVectors(this.pointerStart, this.pointerEnd, true) > clickThres) {
                        if (pressEnabled) {
                            AnimationFrame.cancel(this._pressTimer);
                        }
                        this._clickStart = false;
                        this._doubleClickStart = false;
                        return;
                    }
                    break;
                case 'pointerup':
                    if (!this._clickStart) return;

                    if (pressEnabled) {
                        AnimationFrame.cancel(this._pressTimer);
                    }

                    if (doubleClickEnabled) {
                        this._clickStart = false;
                        if (this._doubleClickStart) {
                            this._doubleClickStart = false;
                            this.doubleClickHandle(pickInfo);
                        } else {
                            this._doubleClickStart = true;
                            AnimationFrame.throttleByTime(this._dblClickTimer, doubleClickThres, () => {
                                this._doubleClickStart = false;
                            }, false);
                            AnimationFrame.throttleByTime(this._clickTimer, doubleClickThres, () => {
                                if (clickEnabled) this.clickHandle(pickInfo);
                            }, false);
                        }
                    } else {
                        this._clickStart = false;
                        if (clickEnabled) this.clickHandle(pickInfo);
                    }

                    break;
            }
        };

        this.isEnabled = ops.isEnabled === undefined ? true : ops.isEnabled;
    }

    /**
     * 设置是否启用
     */
    public set isEnabled(isEnabled: boolean) {
        if (this._isEnabled === isEnabled) return;
        this._isEnabled = isEnabled;
        isEnabled ? this.scene.onPointerObservable.add(this._pointerHandle) : this.scene.onPointerObservable.removeCallback(this._pointerHandle);
    }

    /**
     * 获取是否启用
     */
    public get isEnabled(): boolean {
        return this._isEnabled;
    }

    /**
     * 添加可拾取对象
     * @param targets 目标对象集合
     */
    public add(...targets: T[]): void {
        for (const tar of targets) {
            const idx = this.targets.findIndex(t => this.compare(t, tar));
            if (idx >= 0) continue;
            this.targets.push(tar);
            this.setPickable(true, tar);
        }
    }

    /**
     * 从可拾取对象中移除对象
     * @param targets 目标对象
     */
    public remove(...targets: T[]): void {
        for (const tar of targets) {
            const idx = this.targets.findIndex(t => this.compare(t, tar));
            if (idx < 0) continue;
            this.setPickable(false, tar);
            this.targets.splice(idx, 1);
        }
    }

    /**
     * 清除可拾取对象
     */
    public clear(): void {
        this.remove(...this.targets);
    }

    /**
     * 设置场景所有网格的可拾取属性
     * @param defaultPickable 默认是否可拾取
     */
    public setSceneMeshes(defaultPickable: boolean): void {
        this.scene.meshes.forEach(m => m.isPickable = defaultPickable);
    }

    /**
     * 销毁，清除可拾取对象
     */
    public dispose(): void {
        this.isEnabled = false;
        this.clear();
    }

    /**
     * 指针按下时的响应函数
     * @param pickInfo 拾取信息
     */
    protected downHandle(pickInfo: Nullable<PickingInfo>): void { }

    /**
     * 指针移动时的响应函数
     * @param pickInfo 拾取信息
     */
    protected moveHandle(pickInfo: Nullable<PickingInfo>): void { }

    /**
     * 指针按住时的响应函数
     * @param pickInfo 拾取信息
     */
    protected pressHandle(pickInfo: Nullable<PickingInfo>): void { }

    /**
     * 点击时的响应函数
     * @param pickInfo 拾取信息
     */
    protected clickHandle(pickInfo: Nullable<PickingInfo>): void { }

    /**
     * 双击时的响应函数
     * @param pickInfo 拾取信息
     */
    protected doubleClickHandle(pickInfo: Nullable<PickingInfo>): void { }

    /**
     * 从对象中获取网格
     * @param target 目标对象
     */
    protected abstract getMeshesFromTarget(target: T): AbstractMesh[];

    /**
     * 拾取对象的对比函数
     * @param a 拾取对象1
     * @param b 拾取对象2
     */
    protected abstract compare(a: T, b: T): boolean;

    /**
     * 设置拾取对象的网格的可拾取属性
     * @param isPickable 是否可拾取
     * @param target 拾取对象
     */
    private setPickable(isPickable: boolean, target: T): void {
        const ms = this.getMeshesFromTarget(target);
        ms.forEach(m => m.isPickable = isPickable);
    }

}


export { AbstractPick };
export type { IAbstractPickOptions };